using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Common.Utilities;
using Common.Utilities.Dispatcher;
using PdfLib;

namespace Workflows.Components.TextMining.PdfBookmarkExtraction
{
    /// <summary>
    /// 
    /// </summary>
    public class PdfBookmarkExtractionWorker:IWorker
    {
        #region fields
        private Thread _WorkerThread;
        private PdfBookmarkExtractionInstruction _Instruction;
        private PdfBookmarkExtractionOutput _Output;
        private Guid _JobTicketID;
        private RunContext _Context;
        private string _WorkflowName;
        private string _ComponentName;

        /// <summary>
        /// 
        /// </summary>
        public string ComponentName
        {
            get { return _ComponentName; }
        }

        /// <summary>
        /// 
        /// </summary>
        public string WorkflowName
        {
            get { return _WorkflowName; }
        }

        #endregion

        #region IWorker Members

        public Thread WorkerThread
        {
            get { return _WorkerThread; }
        }

        public InstructionBase Instruction
        {
            get { return _Instruction; }
        }

        public ResultBase Output
        {
            get { return _Output; }
        }

        public Type ComponentType
        {
            get { return typeof(PdfBookmarkExtractionComponent); }
        }

        public RunContext Context
        {
            get { return _Context; }
            set { _Context = value; }
        }

        public void SetInstruction(Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfBookmarkExtractionInstruction)request;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void SetInstruction(RunContext context, Guid ticketGuid, InstructionBase request, string workflowName, string componentName)
        {
            this._JobTicketID = ticketGuid;
            this._Instruction = (PdfBookmarkExtractionInstruction) request;
            this._Context = context;
            this._WorkflowName = workflowName;
            this._ComponentName = componentName;
        }

        public void DoWork()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = Thread.CurrentThread;
            this.ExtractBookmarkContents();
        }


        public void DoWorkAsync()
        {
            if (this.WorkerStartedEvent != null)
            {
                this.WorkerStartedEvent(this._JobTicketID, this, this.GetType(), this._Instruction);
            }
            this._WorkerThread = new Thread(new ThreadStart(this.ExtractBookmarkContents));
            this._WorkerThread.Start();
        }

        public void CancelWork()
        {
            if (this._WorkerThread != null && this._WorkerThread.IsAlive)
            {
                this._WorkerThread.Abort();
                if (this.WorkerCancelledEvent != null)
                {
                    this.WorkerCancelledEvent(this._JobTicketID, this.GetType(), this._Instruction);
                }
            }

        }

        public event WorkerStarted WorkerStartedEvent;
        public event WorkerFinished WorkerFinishedEvent;
        public event WorkerHasError WorkerErredEvent;
        public event WorkerCancelled WorkerCancelledEvent;
        public event WorkerStatusChanged WorkerStatusChangedEvent;

        #endregion

        #region work
        private void ExtractBookmarkContents()
        {
            SqlConnection srcConn=new SqlConnection(this._Instruction.SrcConnStr);
            SqlConnection tgtConn = new SqlConnection(this._Instruction.TgtConnStr);
            
            try
            {
                TableColumn[] cols =
                DbUtil.GetTableColumns(DataProviderType.MSSQL, this._Instruction.TgtConnStr,
                                       string.Empty, this._Instruction.TargetTableName);
                List<TableColumn> tgtCols = new List<TableColumn>();
                Dictionary<string, SqlParameter> parameters = new Dictionary<string, SqlParameter>();
                foreach (TableColumn col in cols)
                {
                    if (col.ColumnName.ToLower() == this._Instruction.TgtIDField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.TgtIDField, new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.BookmarkContentFieldName.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.BookmarkContentFieldName,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.ExtractFromPageField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.ExtractFromPageField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.ExtractFromPosField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.ExtractFromPosField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.ExtractToPageField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.ExtractToPageField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.ExtractToPosField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.ExtractToPosField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.FlagField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.FlagField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                    else if (col.ColumnName.ToLower() == this._Instruction.UpdField.ToLower())
                    {
                        tgtCols.Add(col);
                        parameters.Add(this._Instruction.UpdField,
                                       new SqlParameter("@" + col.ColumnName, DBNull.Value));
                    }
                }
                string spName = "bulkInsert_" + this._Instruction.TargetTableName + "_SP";
                string spBody = StoredProcUtil.CreateStoredProcedureStatementForInsert(
                    DataProviderType.MSSQL, spName, string.Empty, this._Instruction.TargetTableName,
                    tgtCols.ToArray());
                DbUtil.NewStoredProc(this._Instruction.TgtConnStr, spName, spBody);
                int totalSrcRecordCount = DbUtil.GetTableRecordCount(
                    DataProviderType.MSSQL, this._Instruction.SrcConnStr, this._Instruction.SelectSql);

                srcConn.Open();
                SqlCommand srcCmd = srcConn.CreateCommand();
                srcCmd.CommandType = CommandType.Text;
                srcCmd.CommandText = this._Instruction.SelectSql;
                SqlDataReader reader = srcCmd.ExecuteReader();
                tgtConn.Open();
                SqlCommand tgtCmd = tgtConn.CreateCommand();
                tgtCmd.CommandType = CommandType.Text;
                tgtCmd.CommandText = string.Format("select {0} from {1} where {2}={3}",
                                                   this._Instruction.TgtIDField,
                                                   this._Instruction.TargetTableName,
                                                   this._Instruction.FlagField,
                                                   (int) BookmarkExtractionFlag.Successful);
                SqlDataReader tgtReader = tgtCmd.ExecuteReader();
                List<string> existingKeys = new List<string>();
                while (tgtReader.Read())
                {
                    object existingKey = tgtReader[0];
                    if (existingKey != null && !existingKeys.Contains(existingKey.ToString().ToUpper()))
                    {
                        existingKeys.Add(existingKey.ToString().ToUpper());
                    }
                }
                tgtReader.Close();

                tgtCmd.CommandType = CommandType.StoredProcedure;
                tgtCmd.CommandText = spName;

                int fileProcessed = 0;
                int contentExtracted = 0;
                while (reader.Read())
                {
                    try
                    {
                        if (reader[this._Instruction.SrcIDField] != null && reader[this._Instruction.FilePathField] != null)
                        {
                            BookmarkExtractionFlag flag = BookmarkExtractionFlag.Unknown;
                            object id = reader[this._Instruction.SrcIDField];
                            if (existingKeys.Contains(id.ToString().ToUpper()))
                            {
                                fileProcessed++;
                                if (this.WorkerStatusChangedEvent != null)
                                {
                                    int pct = fileProcessed * 100 / totalSrcRecordCount;
                                    string msg = string.Format("Processing {0} of {1} files, {2} bookmarks extracted",
                                                               fileProcessed, totalSrcRecordCount, contentExtracted);
                                    this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName, msg, pct);
                                }
                                continue;
                            }

                            tgtCmd.Parameters.Clear();
                            foreach(string key in parameters.Keys)
                            {
                                parameters[key].Value = DBNull.Value;
                            }
                            parameters[this._Instruction.TgtIDField].Value = id;

                            string filePath = reader[this._Instruction.FilePathField].ToString();
                            if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
                            {
                                PdfBookmark bookmark = this.GetBookmark(filePath,
                                                                        this._Instruction.BookmarkTitle,
                                                                        this._Instruction.BookmarkPath,
                                                                        this._Instruction.BookmarkSearchPatterns);
                                PdfBookmark toBookmark = null;
                                if(this._Instruction.AcrossMultipleBookmarks)
                                {
                                    toBookmark = this.GetBookmark(filePath, this._Instruction.ToBookmarkTitle,
                                                                  this._Instruction.ToBookmarkPath,
                                                                  this._Instruction.ToBookmarkPatterns);
                                }
                                if (bookmark != null)
                                {
                                    if(parameters.ContainsKey(this._Instruction.ExtractFromPageField))
                                    {
                                        parameters[this._Instruction.ExtractFromPageField].Value =
                                            bookmark.FromPageNumber;
                                    }
                                    if (parameters.ContainsKey(this._Instruction.ExtractFromPosField))
                                    {
                                        parameters[this._Instruction.ExtractFromPosField].Value =
                                            string.Format("{0},{1}", 
                                                          bookmark.FromPosition.X,
                                                          bookmark.FromPosition.Y);
                                    }
                                    if(parameters.ContainsKey(this._Instruction.ExtractToPageField))
                                    {
                                        if(toBookmark !=null)
                                        {
                                            parameters[this._Instruction.ExtractToPageField].Value =
                                                toBookmark.FromPageNumber;
                                        }
                                        else
                                        {
                                            parameters[this._Instruction.ExtractToPageField].Value =
                                                bookmark.ToPageNumber;
                                        }
                                    }
                                    if (parameters.ContainsKey(this._Instruction.ExtractToPosField))
                                    {
                                        if(toBookmark !=null)
                                        {
                                            parameters[this._Instruction.ExtractToPageField].Value =
                                                string.Format("{0},{1}",
                                                              toBookmark.FromPosition.X,
                                                              toBookmark.FromPosition.Y);
                                        }
                                        else
                                        {
                                            parameters[this._Instruction.ExtractToPosField].Value =
                                                string.Format("{0},{1}",
                                                              bookmark.ToPosition.X,
                                                              bookmark.ToPosition.Y);
                                        }
                                        
                                    }
                                    string bookmarkContent = string.Empty;
                                    if(toBookmark !=null)
                                    {
                                        bookmarkContent = this.GetBookmarkContent(filePath, bookmark, toBookmark);
                                    }
                                    else
                                    {
                                        bookmarkContent = PdfBookmarkReader.ReadBookmarkContent(filePath, bookmark);
                                    }
                                    string boxedContent = PdfBookmarkExtractionWorker.FilterBookmarkContent(
                                        bookmarkContent, filePath, bookmark.FromPageNumber,
                                        this._Instruction.UseBoxedContent,
                                        this._Instruction.MinWidth, this._Instruction.MinHeight);
                                    string filteredContent = boxedContent;
                                    if(this._Instruction.ContentFilters !=null && this._Instruction.ContentFilters.Count>0)
                                    {
                                        foreach(PdfContentFilter filter in this._Instruction.ContentFilters)
                                        {
                                            bool matchedFilter = false;
                                            if (toBookmark != null)
                                            {
                                                filteredContent = PdfBookmarkExtractionWorker.FilterBookmarkContent(
                                                    filePath, bookmark.FromPageNumber, toBookmark.FromPageNumber,
                                                    filteredContent, filter, ref matchedFilter);
                                            }
                                            else
                                            {
                                                filteredContent = PdfBookmarkExtractionWorker.FilterBookmarkContent(
                                                    filePath, bookmark.FromPageNumber, bookmark.ToPageNumber,
                                                    filteredContent, filter, ref matchedFilter);
                                            }
                                            if (matchedFilter)
                                                break;
                                        }
                                    }
                                    parameters[this._Instruction.BookmarkContentFieldName].Value = filteredContent;

                                    if (!string.IsNullOrEmpty(filteredContent))
                                    {
                                        contentExtracted++;
                                        flag = BookmarkExtractionFlag.Successful;
                                    }
                                    else
                                    {
                                        flag = BookmarkExtractionFlag.BookmarkContentNotAvailable;
                                    }
                                }
                                else
                                {
                                    flag = BookmarkExtractionFlag.UnableToFindBookmark;
                                }
                            }
                            else
                            {
                                flag = BookmarkExtractionFlag.FileError;
                            }
                            if (!string.IsNullOrEmpty(this._Instruction.FlagField) && 
                                parameters.ContainsKey(this._Instruction.FlagField))
                                parameters[this._Instruction.FlagField].Value = (int) flag;
                            if(!string.IsNullOrEmpty(this._Instruction.UpdField) &&
                                parameters.ContainsKey(this._Instruction.UpdField))
                                parameters[this._Instruction.UpdField].Value = DateTime.Now;

                            foreach (SqlParameter parameter in parameters.Values)
                                tgtCmd.Parameters.Add(parameter);
                            tgtCmd.ExecuteNonQuery();
                        }
                    }
                    catch (Exception ex1)
                    {
                        if (this.WorkerErredEvent != null)
                            this.WorkerErredEvent(this._JobTicketID, this.GetType(),
                                                  this._Instruction, ex1.ToString(), false);
                    }
                    
                    fileProcessed++;
                    if(this.WorkerStatusChangedEvent !=null)
                    {
                        int pct = fileProcessed * 100 / totalSrcRecordCount;
                        string msg = string.Format("Processing {0} of {1} files, {2} bookmarks extracted",
                                                   fileProcessed, totalSrcRecordCount, contentExtracted);
                        this.WorkerStatusChangedEvent(this._JobTicketID, this.GetType(), this._Instruction, this.WorkflowName, this.ComponentName, msg, pct);
                    }
                }
                reader.Close();

                this._Output = new PdfBookmarkExtractionOutput(this._Instruction.OwnerComponentID, this._JobTicketID,
                                                               fileProcessed, contentExtracted);

                if(this.WorkerFinishedEvent !=null)
                {
                    this.WorkerFinishedEvent(this._JobTicketID, this.GetType(), this._Instruction, this._Output);
                }
            }
            catch (Exception ex)
            {
                if(this.WorkerErredEvent !=null)
                {
                    this.WorkerErredEvent(this._JobTicketID, this.GetType(), this._Instruction, ex.ToString(),true);
                }
            }
            finally
            {
                srcConn.Close();
                tgtConn.Close();
            }
        }

       
        #endregion

        #region search bookmark
        private PdfBookmark GetBookmark(string filePath,
            string title, string path, string[] searchPatterns)
        {
            if (!string.IsNullOrEmpty(title))
                return PdfBookmarkReader.FindBookmarkByTitle(filePath, title);
            else if (!string.IsNullOrEmpty(path))
                return PdfBookmarkReader.FindBookmarkByPath(filePath, path);
            else if (searchPatterns != null && searchPatterns.Length > 0)
                return PdfBookmarkReader.FindBookmark(filePath, searchPatterns);
            else
                return null;
        }
        #endregion

        #region read bookmark content 
        private string GetBookmarkContent(string pdfFilePath, PdfBookmark fromBookmark, PdfBookmark toBookmark)
        {
            return PdfTextReader.GetContent(pdfFilePath, fromBookmark.FromPageNumber, fromBookmark.FromPosition,
                                            toBookmark.FromPageNumber, toBookmark.FromPosition);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfFilePath"></param>
        /// <param name="fromPageNum"></param>
        /// <param name="toPageNum"></param>
        /// <param name="bookmarkContent"></param>
        /// <param name="filter"></param>
        /// <param name="matchedFilter"></param>
        /// <returns></returns>
        public static string FilterBookmarkContent(
           string pdfFilePath,
           int fromPageNum,
           int toPageNum,
           string bookmarkContent,
           PdfContentFilter filter,
           ref bool matchedFilter)
        {
            string filteredContent = bookmarkContent;
            bool headerMatched = false;
            bool footerMatched = false;
            if (!string.IsNullOrEmpty(filter.HeaderPattern))
            {
                string headerFilter = StringUtil.BuildRegexMatchPattern(filter.HeaderPattern);
                if (Regex.IsMatch(filteredContent, headerFilter, RegexOptions.IgnoreCase))
                {
                    headerMatched = true;
                    Match match = Regex.Match(filteredContent, headerFilter, RegexOptions.IgnoreCase);
                    if (filter.SkipHeader)
                    {
                        List<PdfTextLine> pdfLines = PdfTextReader.ReadPageContentAsLines(pdfFilePath, fromPageNum, 8.0);
                        pdfLines.Sort();
                        int startPos = -1;
                        foreach (PdfTextLine line in pdfLines)
                        {
                            Match match2 = Regex.Match(line.LineContent, headerFilter, RegexOptions.IgnoreCase);
                            if (match2.Success)
                            {
                                if (filteredContent.IndexOf(line.LineContent) >= 0)
                                {
                                    startPos = filteredContent.IndexOf(line.LineContent) + line.LineContent.Length;
                                }
                            }
                        }
                        if (startPos > 0)
                            filteredContent = filteredContent.Substring(startPos);
                        else
                            filteredContent = filteredContent.Substring(match.Index + match.Value.Length);
                    }
                    else
                    {
                        List<PdfTextLine> pdfLines = PdfTextReader.ReadPageContentAsLines(pdfFilePath, toPageNum, 8.0);
                        pdfLines.Sort();
                        int startPos = -1;
                        foreach (PdfTextLine line in pdfLines)
                        {
                            Match match2 = Regex.Match(line.LineContent, headerFilter, RegexOptions.IgnoreCase);
                            if (match2.Success)
                            {
                                startPos = filteredContent.IndexOf(line.LineContent);
                            }
                        }
                        if (startPos > 0)
                            filteredContent = filteredContent.Substring(startPos);
                        else
                            filteredContent = filteredContent.Substring(match.Index);
                    }
                }
                else
                {
                    string matchedString = string.Empty;
                    double fuzzyMatchScore =
                        StringUtil.FuzzyStringMatch(
                            bookmarkContent,
                            new string[] { filter.HeaderPattern },
                            ref matchedString,
                            false);
                    if (fuzzyMatchScore > 0.9)
                    {
                        headerMatched = true;
                        int startPos = filteredContent.IndexOf(matchedString);
                        if (startPos >= 0)
                        {
                            if (filter.SkipHeader)
                            {
                                filteredContent = filteredContent.Substring(startPos + matchedString.Length);
                            }
                            else
                            {
                                filteredContent = filteredContent.Substring(startPos);
                            }
                        }
                    }
                    else
                    {
                        headerMatched = false;
                    }
                }
            }
            else
            {
                headerMatched = true;
            }
            if (!string.IsNullOrEmpty(filter.FooterPattern))
            {
                string footerFilter = StringUtil.BuildRegexMatchPattern(filter.FooterPattern);
                if (Regex.IsMatch(filteredContent, footerFilter, RegexOptions.IgnoreCase))
                {
                    footerMatched = true;
                    Match match = Regex.Match(filteredContent, footerFilter, RegexOptions.IgnoreCase);
                    if (filter.SkipFooter)
                    {
                        List<PdfTextLine> pdfLines = PdfTextReader.ReadPageContentAsLines(pdfFilePath, toPageNum, 8.0);
                        pdfLines.Sort();
                        int startPos = -1;
                        foreach (PdfTextLine line in pdfLines)
                        {
                            Match match2 = Regex.Match(line.LineContent, footerFilter, RegexOptions.IgnoreCase);
                            if (match2.Success)
                            {
                                startPos = filteredContent.IndexOf(line.LineContent);
                            }
                        }
                        if (startPos > 0)
                            filteredContent = filteredContent.Substring(0, startPos);
                        else
                            filteredContent = filteredContent.Substring(0, match.Index);
                    }
                    else
                    {
                        List<PdfTextLine> pdfLines = PdfTextReader.ReadPageContentAsLines(pdfFilePath, toPageNum, 8.0);
                        pdfLines.Sort();
                        int startPos = -1;
                        foreach (PdfTextLine line in pdfLines)
                        {
                            Match match2 = Regex.Match(line.LineContent, footerFilter, RegexOptions.IgnoreCase);
                            if (match2.Success)
                            {
                                if (filteredContent.IndexOf(line.LineContent) >= 0)
                                {
                                    startPos = filteredContent.IndexOf(line.LineContent) + line.LineContent.Length;
                                }
                            }
                        }
                        if (startPos > 0)
                            filteredContent = filteredContent.Substring(0, startPos);
                        else
                            filteredContent = filteredContent.Substring(0, match.Index + match.Value.Length);
                    }
                }
                else
                {
                    string matchedString = string.Empty;
                    double fuzzyMatchScore =
                        StringUtil.FuzzyStringMatch(
                            bookmarkContent,
                            new string[] { filter.FooterPattern },
                            ref matchedString,
                            false);
                    if (fuzzyMatchScore > 0.9)
                    {
                        footerMatched = true;
                        int startPos = filteredContent.IndexOf(matchedString);
                        if (startPos >= 0)
                        {
                            if (filter.SkipFooter)
                            {
                                filteredContent = filteredContent.Substring(0, startPos);
                            }
                            else
                            {
                                filteredContent = filteredContent.Substring(0, startPos + matchedString.Length);
                            }
                        }
                    }
                    else
                    {
                        footerMatched = false;
                    }
                }
            }
            else
            {
                footerMatched = true;
            }

            if (headerMatched && footerMatched)
                matchedFilter = true;
            else
                matchedFilter = false;

            return filteredContent;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bookmarkContent"></param>
        /// <param name="pdfFilePath"></param>
        /// <param name="pageNum"></param>
        /// <param name="useBoxedContent"></param>
        /// <param name="minW"></param>
        /// <param name="minH"></param>
        /// <returns></returns>
        public static string FilterBookmarkContent(string bookmarkContent,
            string pdfFilePath, int pageNum,
            bool useBoxedContent, float minW, float minH)
        {
            if (useBoxedContent)
            {
                bookmarkContent = PdfTextReader.ReadBoxedContent(pdfFilePath, pageNum, minW, minH);
            }
            return bookmarkContent;
        }
        #endregion
    }
}
